各位發現了嗎?在寫完CRUD後,打開CreateStudentView.kt和EditStudent.kt兩相對照,新增和編緝的畫面幾乎一樣,能不能共用程式碼?怎麼共用?可不可以寫成日後可重複使用的組件?
StudentEditorComponent.kt,將EditStudent.kt複制進來後,給student一個set()方法,讓這個component可設定student屬性package com.example.vok
import com.github.mvysny.karibudsl.v10.*
import com.vaadin.flow.component.HasComponents
class StudentEditorComponent: KComposite() {
    private val binder = beanValidationBinder<Student>()
    var student: Student? = null
        set(value) {
            field = value
            value?.let { binder.readBean(value) }
        }
    private val root = ui {
        verticalLayout {
            isMargin = false
            textField("姓名 : "){
                bind(binder).bind(Student::name)
            }
            comboBox<Gender>("性別 : "){
                setItems(*Gender.values())
                bind(binder).bind(Student::gender)
            }
            datePicker("生日 : "){
                bind(binder).bind(Student::birthday)
            }
            numberField("身高"){
                bind(binder).bind(Student::height)
            }
            numberField("體重"){
                bind(binder).bind(Student::weight)
            }
            button("儲存"){
                onLeftClick {
                    val student = student!!
                    if (binder.validate().isOk && binder.writeBeanIfValid(student)){
                        student.save()
                        StudentView.navigateTo(student.id!!)
                    }
                }
            }
            routerLink(null, "返回", AllStudentsView::class)
        }
    }
}
fun HasComponents.studentEditorComponent(block: StudentEditorComponent.()->Unit = {}) = init(StudentEditorComponent(), block)
最後一行,HasComponents.studentEditorComponent() Extension function,參數為要加入組件的程式碼區段,再將組件回傳。
CreateStudentView.kt,移除重複的程式碼,由於此畫面是新增學生資料,所以指定 student 屬性為 Student(),改好後程式如下 :class CreateStudentView: KComposite() {
    private lateinit var editorComponent: StudentEditorComponent
    private val root = ui {
        verticalLayout {
            h1("新增學生資料")
            editorComponent = studentEditorComponent {
                student = Student()
            }
        }
    }
}
看起來是不是變得很簡潔?
EditStudent.kt,這裡也一樣移除重複的程式碼後加上StudentEditorComponent    private lateinit var studentEditorComponent: StudentEditorComponent
    private val root = ui {
        verticalLayout {
            h1("學生資料修改")
            studentEditorComponent = studentEditorComponent()
        }
    }
    override fun setParameter(event: BeforeEvent?, studentId: Long?) {
        studentEditorComponent.student = Student.getById(studentId!!)
    }
這段原本重新取讀 bean 讓 grid 自動更新的程式碼,改為設定組件的student屬性值。
執行後的結果和原本一樣一樣,畫面組件化後程式碼不但可讀性更高,且組件和原本程式碼脫勾相互不受影響。
我們先來看看,如果今天想要在grid裡使用帶clicklistener的圖型按鍵需要做哪些事。開啟AllStudentsView.kt
    addColumn(ComponentRenderer<Button, Student>{ student: Student ->
        val button = Button(VaadinIcon.EYE.create())
        button.addThemeVariants(ButtonVariant.LUMO_ICON, ButtonVariant.LUMO_TERTIARY, ButtonVariant.LUMO_SMALL)
        button.onLeftClick { StudentView.navigateTo(student.id!!)}
        button
    }).setWidth("50px").isExpand = false
                
加上三個帶事件的 button column 後,就會變成這樣
    addColumn(ComponentRenderer<Button, Student>{ student: Student ->
        val button = Button(VaadinIcon.EYE.create())
        button.addThemeVariants(ButtonVariant.LUMO_ICON, ButtonVariant.LUMO_TERTIARY, ButtonVariant.LUMO_SMALL)
        button.onLeftClick { StudentView.navigateTo(student.id!!)}
        button
    }).setWidth("50px").isExpand = false
    addColumn(ComponentRenderer<Button, Student>{ student: Student ->
        val button = Button(VaadinIcon.EDIT.create())
        button.addThemeVariants(ButtonVariant.LUMO_ICON, ButtonVariant.LUMO_TERTIARY, ButtonVariant.LUMO_SMALL)
        button.onLeftClick { EditStudent.navigateTo(student.id!!)}
        button
    }).setWidth("50px").isExpand = false
    addColumn(ComponentRenderer<Button, Student>{ student: Student ->
        val button = Button(VaadinIcon.TRASH.create())
        button.addThemeVariants(ButtonVariant.LUMO_ICON, ButtonVariant.LUMO_TERTIARY, ButtonVariant.LUMO_SMALL)
        button.onLeftClick {
            confirmDialog(text = "是否確定刪除${student.name}的資料?") {
            student.delete()
            this.refresh()
        }}
        button
    }).setWidth("50px").isExpand = false
程式碼看起來不僅兀長且不易閱讀。如果 Grid 能夠直接提供一個方法,只要傳進icon、column key、clickListener三個參數,就能回給我一個帶有listener的圖形按鍵,好像還不錯。
fun <T> Grid<T>.addButtonColumn(icon: IconFactory, key: String, clickListener: (T) -> Unit): Grid.Column<T> {
    val renderer = ComponentRenderer<Button, T> { data: T ->
        val button = Button(icon.create())
        button.addThemeVariants(ButtonVariant.LUMO_ICON, ButtonVariant.LUMO_TERTIARY, ButtonVariant.LUMO_SMALL)
        button.onLeftClick { clickListener(data) }
        button
    }
    val column: Grid.Column<T> = addColumn(renderer).setKey(key).setWidth("50px")
    column.isExpand = false
    return column
}
grid{} 加上圖形按鍵 column 的程式碼改為
        addButtonColumn(VaadinIcon.EYE, "view") { StudentView.navigateTo(it.id!!) }
        addButtonColumn(VaadinIcon.EDIT, "edit") { EditStudent.navigateTo(it.id!!) }
        addButtonColumn(VaadinIcon.TRASH, "delete"){
            confirmDialog(text = "是否確定刪除${it.name}的資料?") {
                it.delete()
                this.refresh()
            }
        }
執行結果如下:
經過Refactor後,整個程式是不是看起來既乾淨又簡捷?
明天開始進入第二張資料表,就要講到資料庫關聯囉~
本日程式已上傳 GitHub